import { ClientMessage } from "@runtime/generate/message/ClientMessage";
import WebSocketComponent from "@runtime/module/WebSocket/WebSocketComponent";
import { reactive, ref, Ref, UnwrapNestedRefs, watch, WatchStopHandle } from "vue";

/**
 * 交互对象组件
 */
export default class InteractableObjectComponent {
    private static instance: InteractableObjectComponent = new InteractableObjectComponent();
    /**
     * 交互对象组件
     */
    public static get Default(): InteractableObjectComponent {
        return this.instance;
    }

    constructor() {
        window.addEventListener('unload', this.Dispose.bind(this));

        this.watchStopHandles.push(watch(this.Choices, this.OnChoicesChangedAsync.bind(this)));
    }

    Dispose() {
        window.removeEventListener('unload', this.Dispose);

        for (const watchStopHandle of this.watchStopHandles) {
            watchStopHandle();
        }
        this.watchStopHandles.clear();
    }

    private watchStopHandles: Array<WatchStopHandle> = [];

    private informations: UnwrapNestedRefs<Map<number, ClientMessage.InteractableObjectInformation>> = reactive(new Map());

    /**
     * Count
     */
    public get Count(): number {
        return this.informations.size;
    }

    /**
     * Informations
     */
    public get Informations(): UnwrapNestedRefs<Map<number, ClientMessage.InteractableObjectInformation>> {
        return this.informations;
    }

    public Choices: Ref<Array<number>> = ref([]);

    public async LoadAsync(types: Array<string>): Promise<boolean> {
        const response: ClientMessage.InitializeInteractableObjectResponse = await WebSocketComponent.Default.PostMessageAsync(new ClientMessage.InitializeInteractableObjectRequest(types));
        return response.Error == 0;
    }

    public async UncheckAsync(id: number): Promise<boolean> {
        const information = this.informations.get(id);
        if (information == undefined) {
            return false;
        }

        const response: ClientMessage.UncheckInteractableObjectResponse = await WebSocketComponent.Default.PostMessageAsync(new ClientMessage.UncheckInteractableObjectRequest(information.Id, information.InstanceId));
        if (response.Error == 0) {
            this.Remove(id);
            return true;
        }

        return false;
    }

    public async ShowLocationAsync(id: number): Promise<boolean> {
        const information = this.informations.get(id);
        if (information == undefined) {
            return false;
        }
        const response: ClientMessage.ShowInteractableObjectLocationResponse = await WebSocketComponent.Default.PostMessageAsync(new ClientMessage.ShowInteractableObjectLocationRequest(id));
        return response.Error == 0;
    }

    public async HideLocationAsync(): Promise<boolean> {
        const response: ClientMessage.HideInteractableObjectLocationResponse = await WebSocketComponent.Default.PostMessageAsync(new ClientMessage.HideInteractableObjectLocationRequest());
        return response.Error == 0;
    }

    public Add(information: ClientMessage.InteractableObjectInformation): void {
        this.informations.set(information.Id, information);
    }

    public Remove(id: number): boolean {
        return this.informations.delete(id);
    }

    public async OnApplyLocationAsync(message: ClientMessage.ApplyInteractableObjectLocationMessage): Promise<void> {
        if (message.Success && this.Choices.value.length > 0) {
            this.Remove(this.Choices.value[0]);
            this.Choices.value.clear();
        }
    }

    private async OnChoicesChangedAsync(current: Array<number>, previous: Array<number>): Promise<void> {
        if (current.length > 1) {
            current.removeAt(0);
        }

        if (current.length == 1) {
            await this.ShowLocationAsync(current[0]);
        }

        if (previous.length == 1 && current.length == 0) {
            await this.HideLocationAsync();
        }
    }
}